package net.yters.model.agents.brains;

import net.yters.model.agents.DeciderAgent;
import net.yters.util.Util;

public class Semantic_DodgeAndKill_Brain extends BrainAgent {

	public Semantic_DodgeAndKill_Brain(DeciderAgent ownerAgent) {
		super(ownerAgent);
	}

	/**
	 * 
	 */
	private static final long serialVersionUID = -3688633670442933123L;

	@Override
	public void execute() {
		Double bulletDistance = (Double) Util.regexGet(".*ClosestBulletAgentSensor.*:distance", f_ownerAgent.f_state).f_value; 
		Double bulletAngle = normalizeBearing((Double) Util.regexGet(".*ClosestBulletAgentSensor.*:angle", f_ownerAgent.f_state).f_value);
		Double enemyDistance = (Double) Util.regexGet(".*ClosestGoalAgentSensor.*:distance", f_ownerAgent.f_state).f_value; 
		Double enemyAngle = normalizeBearing((Double) Util.regexGet(".*ClosestGoalAgentSensor.*:angle", f_ownerAgent.f_state).f_value); 

		Double ownerAgentAngle = normalizeBearing(convertDirectionToAngle(Util.disemBoxDoubleArray(f_ownerAgent.f_direction))); 

		double bulletBearingFromAgent = bearingFrom(bulletAngle, ownerAgentAngle);
		int discretizedBulletBearing = discretizeAngle(bulletBearingFromAgent);
		double enemyBearingFromAgent = bearingFrom(enemyAngle, ownerAgentAngle);
		int discretizedEnemyBearing = discretizeAngle(enemyBearingFromAgent);
		double discretizedEnemyBearingError = discretizedAngleError(enemyBearingFromAgent);

		double cowardDistance = 6.0;
		double attackMaxDistance = 7.0;
		double attackMinDistance = 2.0;

		if(Math.abs(bulletDistance - cowardDistance) <= cowardDistance && Math.random() > .5) { // NOTE Randomize retreat, since constant retreat can get agent killed.
//			System.out.println("Dodging bullet");  // DEBUG

			switch(discretizedBulletBearing) {
			case 0:
			case 4:
				f_choice.f_value = Math.random() > .5 ? 1.0 : 2.0; // NOTE Bullet is in front, so turn to one side.
				break;
			case 1:
			case 2:
			case 3:
				f_choice.f_value = Math.random() > 0.1 ? 0.0 : Math.random() > 0.5 ? 1.0 : 2.0; // NOTE Bullet is to the side, or behind, so usually go forward, but randomly turn sometimes to avoid getting stuck.
				break;
			}
		} else if (enemyDistance >= attackMinDistance && Math.random() > 0.05){ // NOTE Doesn't attack if too close, cuz can't shoot.
//			System.out.println("Attacking enemy");  // DEBUG

			switch(discretizedEnemyBearing) {
			case 0:
			case 4:
				if(enemyDistance <= attackMaxDistance && discretizedEnemyBearingError == 0) {
					f_choice.f_value = 3.0; // NOTE Agent is facing enemy within distance, so shoot it.
				} else {
					f_choice.f_value = 0.0; // NOTE Agent is facing enemy out of distance, so move towards it.
				}
				break;
			case 2:
				f_choice.f_value = Math.random() > 0.5 ? 1.0 : 2.0; // NOTE Enemy is directly behind, so turn in an arbitrary direction.
				break;
			case 1:
				f_choice.f_value = 2.0; // NOTE Enemy is to the right, so turn right.
				break;
			case 3:
				f_choice.f_value = 1.0; // NOTE Enemy is to the left, so turn left.
				break;
			}
		} else { // NOTE Wander around if too close to enemy and not threatened by bullets.
//			System.out.println("Wandering around");  // DEBUG
			f_choice.f_value = Math.random() > 0.5 ? 0.0 : Math.random() > 0.9 ? -2.0 : Math.random() > 0.5 ? 1.0 : 2.0; // NOTE Bullet is to the side, or behind, so usually go forward, but randomly turn sometimes to avoid getting stuck. 
		}

//		System.out.println("bulletDistance: " + bulletDistance); // DEBUG
//		System.out.println("bulletAngle: " + bulletAngle); // DEBUG
//		System.out.println("enemyDistance: " + enemyDistance); // DEBUG
//		System.out.println("enemyAngle: " + enemyAngle); // DEBUG
//		System.out.println("ownerAgentAngle: " + ownerAgentAngle); // DEBUG 
//		System.out.println("bearing from bullet: " + bulletBearingFromAgent); // DEBUG
//		System.out.println("bearing from enemy: " + enemyBearingFromAgent); // DEBUG
//		System.out.println("discrete bearing from bullet: " + discretizedBulletBearing); // DEBUG
//		System.out.println("discrete bearing from enemy: " + discretizedEnemyBearing); // DEBUG
//		System.out.println("choice: " + f_choice.f_value); // DEBUG
//		System.out.println(); // DEBUG
	}

	public static Double convertDirectionToAngle(double[] dir) {
		return Math.atan2(dir[0], dir[1]);
	}

	public static int discretizeAngle(Double angle) {
		return ((int) Math.round(angle * 2/Math.PI))%5;
	}

	public static double discretizedAngleError(Double angle) {
		return Math.abs(angle * 2/Math.PI - Math.round(angle * 2/Math.PI))/4;
	}

	public static double rotate(double bearing, double angle) {
		double maxAngle = 2 * Math.PI;
		double minAngle = 0;
		double fullRotation = maxAngle - minAngle;
		double newBearing = bearing + angle;
		while(newBearing > maxAngle) newBearing -= fullRotation;
		while(newBearing <= minAngle) newBearing += fullRotation;
		return newBearing;
	}

	public static double normalizeBearing(double bearing) {
		return rotate(bearing, 0.0);
	}

	public static double bearingFrom(double a, double b) {
		double result = normalizeBearing(a - b);
//		if(result > Math.PI) {
//		result = result - (2*Math.PI) ;  // NOTE Subtract PI to give negative value for contrary rotation.
//		}
		return result;
	}
}